stb_image.h
第三天-驅動OpenGL這篇有稍稍提到這個玩意
Single-header file
並推薦了stb這個在github上的repo。這個repo放的是作者Sean T. Barrett的撰寫C語言時的各種工具。這些工具最大的特點就是把實作跟宣告寫在同一個文件,利用macro分割開來,這樣可以解決C與C++中麻煩的引用問題。
專案中要使用stb_image.h
是一個「使用起來」簡單,載入處理一些常見的圖檔的函式庫。
老樣子,我把這個文件放在external
資料夾裡面,再額外包一層stb
的資料夾。然後就可以開始載入圖片了。
圖片我用的是Kenny上的Animal Pack Redux,這是一個很讚的網站,裡面提供了許多免費的素材~
首先,定義一個結構(struct
)把要的資料記錄下來
// iron_types.h
typedef enum GL_TextureFmt{
TEX_FMT_WHITE_BLACK = GL_RED,
TEX_FMT_GRAYSCALE = GL_RG,
TEX_FMT_RGB = GL_RGB,
TEX_FMT_RGBA = GL_RGBA,
} TextureFmt;
typedef struct Texture {
unsigned int id;
int w, h;
TextureFmt gl_fmt;
} Texture;
然後,我還定義了一個enum,表示Texture的對應在OpenGL裡面的格式,其實可以點實作進去看,其實就是OpenGL,定義的一系列的數字,用於識別圖片格式。
再來就是載入了...
// iron_asset.c - LoadTextureFile
int channels;
unsigned char* pixels = stbi_load(file_name, &texture->w, &texture->h, &channels, 0);
if (pixels == NULL) {
stbi_image_free(pixels);
printf("Failed to load image: %s\n", file_name);
return RES_ERROR_LOAD_IMAGE_FILE;
}
int src_img_fmt;
if (channels == 2) {
texture->gl_fmt = TEX_FMT_GRAYSCALE;
src_img_fmt = GL_RG8;
} else if (channels == 3) {
texture->gl_fmt = TEX_FMT_RGB;
src_img_fmt = GL_RGB8;
} else if (channels == 4) {
texture->gl_fmt = TEX_FMT_RGBA;
src_img_fmt = GL_RGBA8;
} else {
texture->gl_fmt = TEX_FMT_WHITE_BLACK;
src_img_fmt = GL_R8;
}
// continue...
有用到了就兩個,stbi_load
跟stbi_free
,stbi
可以視為他的命名空間。
stbi_load
最後會回傳圖片所有的像素點,存成unsigned char
的陣列,可以看到的第三個參數我代入channels
,這個就是圖片的顏色通道,例如PNG有RGBA四個通道,那channels就會拿到「4」,最後在對應到OpenGL的格式。除了OpenGL的格式,我還存了圖片的「原」格式,下面會用到
再來就是設置Texture,把他儲存在OpenGL的狀態機裡面:
// iron_asset.c - LoadTextureFile
// part 1
glBindTexture(GL_TEXTURE_2D, 0);
glGenTextures(1, &texture->id);
glBindTexture(GL_TEXTURE_2D, texture->id);
// part 2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// part 3
glTexImage2D(GL_TEXTURE_2D, 0, src_img_fmt, texture->w, texture->h, 0, texture->gl_fmt, GL_UNSIGNED_BYTE, pixels);
// part 4
glBindTexture(GL_TEXTURE_2D, 0);
這裡分作4個Part講解比較容易
Part1: 生成與綁定新的Texture,第一行的bind texture是為了防止之後,有正在使用的texture,被蓋到才加上的
Part2: 設置Texture的環繞(?)與過濾(?)的方式,老實說我不知道中文怎麼翻譯,建議可以看看這個可以更加清楚。
Part3: 設置資料到OpenGL裡,第2個參數必填0 參考
Part4: 設置完成,解除綁定
這樣就把圖片設置在OpenGL裡囉~之後可以通過Texture.id
來呼叫。
之前寫在iron_render.c
內部的shader code,需要加上兩個參數
1.Texture的座標
2.Texture本身
這是更新後的樣子:
static const char* DEFAULT_2D_VERTEX_SHADER_CODE = "#version 330 core\n"
"in vec2 _Pos;\n"
"in vec2 _Texcoords;\n" // 1.
"out vec2 Texcoords;\n" // 2.
"void main() {\n"
" gl_Position = vec4(_Pos, 0.0, 1.0);\n"
" Texcoords = _Texcoords;\n" // 3.
"}\n\0";
static const char* DEFAULT_2D_FRAGMENT_SHADER_CODE = "#version 330 core\n"
"in vec2 Texcoords;\n" // 4.
"uniform vec4 _Color;\n"
"uniform sampler2D _Texture2D;" // 5.
"out vec4 _FragColor;\n"
"void main() {\n"
" _FragColor = _Color * texture(_Texture2D, Texcoords);\n" // 6.
"}\n\0";
然後更新iron_render
模組內,預設的shader,在iron_asset.LoadShaderCode
的地方加上這個
// texture coordinate
int texcoord_location = glGetAttribLocation(shader->id, "_Texcoords");
if (texcoord_location < 0) {
return RES_ERROR_GET_SHADER_ATTRIBUTE;
}
shader->attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD] = texcoord_location;
// texture
int texture_location = glGetUniformLocation(shader->id, "_Texture2D");
if (texture_location < 0) {
return RES_ERROR_GET_SHADER_ATTRIBUTE;
}
shader->attribs_locations[SHADER_ATTRIB_SAMPLER2D_TEXTURE] = texcoord_location;
P.S. 寫的時候發現不應該沒有找到這個屬性就Return,不然之後有不同需求的Shader,裡面沒有包含這屬性就GG了。
最後...利用之前DrawFirstRectangle
的code,改造一下,就可以畫出我們第一個圖片了
unsigned int vbo, vao, ibo;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES), VERTICES, GL_STATIC_DRAW);
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(INDICES), INDICES, GL_STATIC_DRAW);
// set position attribute
glVertexAttribPointer(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_POS], 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_POS]);
// set texture coordinate attribute
glVertexAttribPointer(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD], 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC2_TEXCOORD]);
glUseProgram(RENDER_2D_CONTEXT.default_shader.id);
// set shader color
V4f v = ColorToVec4f(c);
glUniform4f(RENDER_2D_CONTEXT.default_shader.attribs_locations[SHADER_ATTRIB_VEC4_COLOR], v.r, v.g, v.b, v.a);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture->id);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
有個注意一下,原本的VERTICES
,除了頂點位置,我也把Texture的座標放進去了,所以原本的一組頂點是2個float會變成4個float,中間還需要告訴glVertexAttribPointer
頂點與貼圖座標的間距。
完成之後,我的輸出是這樣,發福的倒立兔子
原因有幾個
stbi_set_flip_vertically_on_load
翻轉圖片GL_BLEND
打開,可以在CreateRenderer
的地方補上
// ...
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// ...
今天就先這樣,明天預計會寫輸入操作與時間處理的部分